import numpy as np
import matplotlib.pyplot as plt
import Core.Individual as Individual


# 使用遗传算法求𝑓(𝑥)=𝑥 sin(10𝜋𝑥)+2在区间[-1,2]上的最大值,要求结果精度为10^(−5)。

# 适应度函数
def fitness(x):
    return x * np.sin(10 * np.pi * x) + 2


# 计算种群中各个个体的适应度函数
def cal_fitness(p):
    for i in range(len(p)):
        p[i].fitness = fitness(p[i].x)
    return p


# p为种群存储数组, p_gene为"基因数组"
# 将浮点数转换为二进制数
def encode(p):
    p_gene = []
    for i in range(len(p)):
        # print(type(p[i].x))
        p[i].x = int((p[i].x + 1) * ((np.power(2, 19) - 1) / 3))
        # dec = bin(dec)
        # 序列长度不足19补0(019代表输出为19位,不足19位前置补零, 19则不补)
        p[i].x = '{:019b}'.format(p[i].x)
        p_gene.append(p[i])
    # for i in range(len(p_gene)):
    #     print(p_gene[i].x)
    return p_gene


# 解码 , p_res数组为种群个体解码后的数组
def decode(p_mutation):
    p_res = []
    for i in range(len(p_mutation)):
        # ***公式***
        # int()函数不止可以将字符型数值转换为2/8/10/16进制，
        # 它还可以将2-36（除了1） 进制的字符型数值转换为整型数值
        # ***注意此处不要直接操作对象(p_mutation)会改变对象
        temp = Individual.Individual()
        temp.x = int(p_mutation[i].x, 2)
        temp.x = -1 + temp.x * (3 / (np.power(2, 19) - 1))
        p_res.append(temp)
    return p_res


# 初始化种群(以及计算每个个体的适应度)
# p为种群存储数组, n为个体数(hyper_parameter)
def init_population(n):
    p = []
    for i in range(n):
        # "生成一个个体"
        individual = Individual.Individual()
        individual.x = np.random.uniform(-1, 2)  # uniform() 方法将随机生成下一个实数，它在 [x, y] 范围内。
        p.append(individual)
    return p


# 选择操作(轮盘赌)
# p_probability储存概率
# probability_sum存储概率分布(和)
# p_new为选择完的新数组
def choice(p_gene):
    p_choice = []
    p_probability = []
    probability_sum = []
    p_sum = 0
    # 求所有个体适应度的和
    for i in range(len(p_gene)):
        p_sum += p_gene[i].fitness
    # 将概率值写入p_probability数组
    for i in range(len(p_gene)):
        p_probability.append(p_gene[i].fitness / p_sum)
    # 求出概率分布
    for i in range(len(p_gene)):
        if i == 0:
            probability_sum.append(p_probability[i])
        else:
            probability_sum.append(probability_sum[i - 1] + p_probability[i])
    # 开始选择操作
    for i in range(len(p_gene)):
        # 生成一个(0,1)的随机浮点数
        rand = np.random.uniform(0, 1)
        # 将这个浮点数和"概率分布"逐一比对,检查落在哪一块
        for j in range(len(p_gene)):
            if j == 0:
                if rand < probability_sum[j]:
                    p_choice.append(p_gene[j])
                    break
            else:
                if (rand > probability_sum[j - 1]) and (rand < probability_sum[j]):
                    p_choice.append(p_gene[j])
                    break
    # for i in range(len(p_choice)):
    #     print(p_choice[i].x)
    return p_choice


# 交叉操作(内部交叉操作使用一个交叉函数实现)
# 选择后的种群 -> 设置交叉点 -> 交叉率
# 设置交叉基因序列的后半段
# p_cross数组为交叉完得到的新种群
def cross(p_choice, cross_point, cross_rate):
    p_cross = []
    # 合法性判断
    if cross_point < 0 or cross_point > 19:
        return -1
    # 假设交叉完种群数量不变
    for i in range(len(p_choice)):
        # 生成一个(0,1)随机数, 模拟交叉率
        rand = np.random.uniform(0, 1)
        if rand <= cross_rate:
            # 生成两个(0,n)的随机整数, 模拟随机挑选出两个个体进行基因重组
            obj_1 = p_choice[np.random.randint(0, len(p_choice))]
            obj_2 = p_choice[np.random.randint(0, len(p_choice))]
            obj_1 = cross_function(obj_1, obj_2, cross_point)
            p_cross.append(obj_1)
            # p_cross.append(obj_2)
        # 否则当前个体不交叉(基因重组)
        else:
            p_cross.append(p_choice[i])
    # for i in range(len(p_cross)):
    #     print(p_cross[i].x)
    return p_cross


# 交叉函数
# 将第二个个体cross_point后的基因与第一个个体交换, 返回第一个个体
def cross_function(object_1, object_2, cross_point):
    # 注(join方式是以指定方式"加入" -->Python join() 方法用于将序列中的元素以指定的字符连接生成一个新的字符串。)
    cross_res = object_1.x[:cross_point] + object_2.x[cross_point:]

    # 二进制码以字符串存储(不可变序列)
    # temp = object_1.x[cross_point:]
    # object_1.x[cross_point:] = object_2.x[cross_point:]
    # object_2.x[cross_point:] = temp
    object_1.x = cross_res
    return object_1


# 变异操作
def mutation(p_cross, mutant_rate):
    # 首先判断在当前变异率下是否回发生变异
    mutation_num = 19 * len(p_cross) * mutant_rate
    if mutation_num >= 1:
        # 将变异的基因数取整(这里使用向下取整)
        mutation_num = int(mutation_num)
        # 模拟开始:随机变异mutation_num位基因
        for i in range(mutation_num):
            obj_index = np.random.randint(0, len(p_cross))
            gene_index = np.random.randint(0, 19)
            if p_cross[obj_index].x[gene_index] == '0':
                temp_str = p_cross[obj_index].x[:gene_index] + '1' + p_cross[obj_index].x[gene_index + 1:]
                p_cross[obj_index].x = temp_str
            else:
                temp_str = p_cross[obj_index].x[:gene_index] + '0' + p_cross[obj_index].x[gene_index + 1:]
                p_cross[obj_index].x = temp_str
    p_mutation = p_cross
    # for i in range(len(p_mutation)):
    #     print(p_mutation[i].x)
    return p_mutation
